#ifndef __MY_SERVER__
#define __MY_SERVER__
#include  "data.hpp"
#include  "httplib.h"

extern cloud::DataManager* _data;



namespace cloud
{ 
	class Server
	{


		private:
			int _srv_port=9090;                           //设置端口9090,IP地址通常服务器都是0.0.0.0
			std::string _backup_dir="./backup_dir/";
			httplib::Server _srv;                         




		public:
			Server()
			{
				FileUtil(_backup_dir).CreateDirectory();
			}


			bool RunModule()
			{
		                                          //设置请求的处理函数映射            
				_srv.Post("/upload",Upload);          //设置/upload为上传请求和其对应函数.
				_srv.Get("/list",List);               //展示请求
				                                      //下载请求都是/download/a.txt,所以()内的正则表达式表示匹配文件名
				_srv.Get("/download/(.*)",Download);  //启动服务器
				_srv.listen("0.0.0.0",_srv_port);
				return true;
			}


		private:
			//httplib规定映射的函数只有这两个参数,所以静态
			//可以写数据类的单例模式这样就都可以访问,配置指定的上传请求处理接口
			static void Upload(const httplib::Request& req,httplib::Response& rsp)
			{
				if(req.has_file("file")==false)                                 //检查name字段区域标识符是否为"file",不是file(文件)则报错返回
				{
					std::cout<<"upload file data format error\n";
					rsp.status=400;                                               //返回状态码,400错误
					return ;
				}
				httplib::MultipartFormData data = req.get_file_value("file");     //获取区域标识符为file的信息,有filename文件名,content正文内容等
				std::string _backup_dir = "./backup_dir/";
				std::string real_path = _backup_dir+data.filename;
				//std::cout<<real_path<<std::endl;
				if(FileUtil(real_path).Write(data.content) == false)
				{
					std::cout<<"back up failed\n";
					rsp.status = 500;                                               //服务器内部出错
					return ;
				}
				if(_data->Insert(real_path) == false)
				{
					std::cout<<"insert back info failed\n";
					rsp.status=500;
					return ;
				}
				rsp.status=200;              //上传成功
			}





			static std::string Strtime(time_t t)
			{
				return std::asctime(std::localtime(&t));
				//将时间戳转为看得懂的时间格式.
			}
			//查看已备份文件请求处理接口
			static void List(const httplib::Request& req,httplib::Response& rsp)
			{
				std::vector<FileInfo> arry;                              //获取所有备份文件信息到arry中
				if(_data -> SelectAll(&arry) == false)
				{
					std::cout<<"select back up file failed\n";
					rsp.status = 500;
					return ;
				}
				//将文件信息组织成html页面,作为响应正文
				std::stringstream ss;
				ss << "<html>";
				ss << "<head>";                                            //head标签描述头部的一些信息
				ss <<     "<meta http-equiv='Content-Type' content='text/html;charset=uft-8'>";
				ss <<     "<title>Download</title>";                       //title是页面的名称
				ss << "</head>";
				ss << "<body>";                                            //正文
				ss <<     "<h1>Download</h1>";                             //h1,1级标题
				ss <<     "<table>";                                       //table标签是一个表格,其中tr是行标签,td是列标签
				for(auto& e:arry)
				{                                                        //每行显示三列(三个信息)
					ss <<         "<tr>";
					//<a href="/download/a.txt">txt</a></td>,将txt变成一个超链接,点击跳转/download/a.txt
					ss <<            "<td><a href='" << e.url_path << "'>" << e.filename << "</a><td>";   //a,超链接标签
					ss <<            "<td align='right'>" << Strtime(e.back_time) << "</td>";        //right,右对齐
					ss <<            "<td align='right'>" << e.file_size/1024 << "KB </td>";
					ss <<            "</tr>";
				}
				ss <<       "</table>";
				ss <<   "</body>";
				ss << "</html>";
				rsp.set_content(ss.str(),"text/html");                           //设置响应正文为ss,正文类型为text/html
				rsp.status = 200;
			}






			//下载请求处理接口
			static void Download(const httplib::Request& req,httplib::Response& rsp)
			{
				FileInfo info;
				if(_data -> SelectOne(req.path,&info) == false)
				{
					//获取用户要下载的文件的文件信息
					std::cout << "select one back up file info failed\n";
					rsp.status = 404;                                            //找不到资源404
					return ;
				}
				if(info.pack_flag == true)
				{
					//如果文件已压缩则先解压缩
					FileUtil(info.pack_path).Uncompress(info.real_path);
				}   
				/*如果服务器支持断点续传会在第一次下载响应是就告诉客户端
				有accpt-ranges:bytes 头部字段,表示支持单位为字节的断点续传
				还有etag:"xxxx" 头部字段,是文件的唯一标识,用于判断文件是否被修改过
				断点续传时,客户端请求报文有if-range:"xxxx" 头部字段,就是第一次下载服务器响应的etag
				还有range-bytes=200-100 头部字段,表示这次续传的起始位置和结束位置,下载到末尾结束位置为-1
				服务器再次响应则为206状态码,有content-range:bytes 200-1000/67589 头部字段,最后一个数为文件总大小
        */
				if(req.has_header("If-Range"))
				{
					//请求有if-range头部字段则为断点续传请求
					std::string old_etag=req.get_header_value("If-Range");
					std::string now_etag= Stretag(info.real_path);
					if(old_etag==now_etag)
          {
            //获取到的用户请求的续传文件的etag和文件现在的etag比,
						//如果服务器这边文件没有改变则为同一文件可以续传,否则响应200,重传.
						//这里设定etag值为:文件大小-文件最后访问时间,因此 如果在热点判断时间之后申请续传,文件已被压缩最后访问时间被修改,会解压重传,因为etag对不上了

						//httpblib实现了断点续传功能,我们只需要将文件所有内容放入响应正文,
						//然后设置响应状态码206,httplib检测到206会根据客户端请求报文中的range-bytes字段截取指定区间数据响应
						FileUtil(info.real_path).Read(&rsp.body);
						rsp.set_header("Content-Type","application/octet-stream");  //设置正文类型为二进制流
						rsp.set_header("Accept-Ranges","bytes");                    //告诉客户端可以断点续传
						rsp.set_header("ETag",now_etag);
						rsp.status=206;
						return ;
					}
				}
				FileUtil(info.real_path).Read(&rsp.body);
				rsp.set_header("Content-Type","application/octet-stream");      //设置正文类型为二进制流
				rsp.set_header("Accept-Ranges","bytes");                        //告诉客户端可以断点续传
				rsp.set_header("ETag",Stretag(info.real_path));
				rsp.status=200;
			}










			static std::string Stretag(const std::string& filename)
			{
				time_t mtime=FileUtil(filename).MTime();
				size_t fsize=FileUtil(filename).Size();
				std::stringstream ss;
				ss<<fsize<<"-"<<mtime;
				return ss.str();
			}
	};

}
#endif

